export interface StackInfo<T> {
	_father: StackInfo<T> | undefined;
	info: T;
	children: StackInfo<T>[];
}

export type callbackfn<T, D> = (callbackfn: callbackfn<T, D>, value: StackInfo<T>, index: number, list: StackInfo<T>[]) => D

export default class StackList<T> {

	private _origin: StackInfo<T>[] = [];

	private _select: StackInfo<T> | undefined = undefined;

	public get size(): number {
		let length: number = 0;
		this.foreach(() => length++);
		return length;
	}

	public get empty(): boolean {
		return this._origin.length === 0;
	}

	public get origin(): StackInfo<T>[] {
		return this._origin;
	}
	public set origin(value: StackInfo<T>[]) {
		this._origin = value;
	}

	public get select(): StackInfo<T> | undefined {
		return this._select;
	}

	public get selectInfo(): T | undefined {
		return this._select && this._select.info;
	}

	public get selectCheck(): StackInfo<T> {
		const { _select } = this;
		if (_select === undefined) {
			throw new Error("选项为空");
		}
		return _select;
	}

	public get selectInfoCheck(): T {
		return this.selectCheck.info;
	}

	public get list(): T[] {
		const list: T[] = [];
		this.foreach((value) => list.push(value.info));
		return list;
	}

	public foreach(callback: (value: StackInfo<T>) => void): void {
		const foreach = function (list: StackInfo<T>[]) {
			for (const info of list) {
				callback(info);
				foreach(info.children);
			}
		}
		foreach(this._origin);
	}

	public push(info: T): StackInfo<T> {
		const _father = this._select;
		const select = this._select = { _father, info, children: [] };
		(_father === undefined ? this._origin : _father.children).push(select);
		return select;
	}

	public back(): void {
		const select = this._select;
		if (select === undefined) {
			throw new Error("突破栈底");
		}
		this._select = select._father;
	}

	public copy(callbackfn?: (value: T) => T): StackList<T> {
		const { _origin, _select } = this;
		const ret = new StackList<T>();

		const mapTree = function (data: StackInfo<T>[], _father?: StackInfo<T>) {
			return data.map(item => {
				const info = { ...item };
				info.info = callbackfn === undefined ? info.info : callbackfn(info.info);
				info.children = mapTree(info.children, info);
				info._father = _father;
				if (_select === item) {
					ret._select = info;
				}
				return info;
			});
		};

		ret._origin = mapTree(_origin)
		return ret;
	}

	public map<D>(callbackfn: callbackfn<T, D>): D[] {
		const { _origin } = this;

		const mapTree = function (data: StackInfo<T>[]) {
			return data.map((info, i, list) => {
				return callbackfn(callbackfn, info, i, list) as D;
			});
		};

		return mapTree(_origin);
	}
}
